package hn.sigit.logic.general;

import hn.sigit.dao.hnd.cadastre.HND_SpatialZoneDAO;
import hn.sigit.logic.general.GeneralHelperX;
import hn.sigit.logic.general.GeneralHelper;
import hn.sigit.logic.security.Authenticator;
import hn.sigit.model.hnd.administrative.HND_PermitRuleGroup;
import hn.sigit.model.hnd.administrative.HND_RuleOperatorType;
import hn.sigit.model.hnd.administrative.HND_SpatialRule;
import hn.sigit.model.hnd.cadastre.HND_LandUse;
import hn.sigit.model.hnd.cadastre.HND_SpatialZone;
import hn.sigit.model.ladm.spatialunit.LA_Level;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.security.Identity;

import com.vividsolutions.jts.geom.Geometry;


@Name("spatialAnalysisHelper")
@Scope(value = ScopeType.CONVERSATION)
@AutoCreate
public class SpatialAnalysisHelper implements Serializable {
	private static final long serialVersionUID = 1L;
	
	private HND_SpatialZone spatialZone;
	
	private Double distance;
	
	private boolean doDistance = true;
	private boolean doOverlaps = true;
	private boolean doWithin = true;
	private boolean doTouches = true;
	private boolean doCrosses = true;
	private boolean doContains = true;
	
	private List<SpatialAnalysisResult> distanceResults;
	private List<SpatialAnalysisResult> overlapsResults;
	private List<SpatialAnalysisResult> withinResults;
	private List<SpatialAnalysisResult> touchesResults;
	private List<SpatialAnalysisResult> crossesResults;
	private List<SpatialAnalysisResult> containsResults;

	private HND_PermitRuleGroup ruleGroup;
	private LA_Level operand1Level;
	private HND_LandUse operand1LandUse;
	private LA_Level operand2Level;
	private HND_LandUse operand2LandUse;
	
	private Map<HND_SpatialRule, List<SpatialAnalysisResult>> spatialRuleToAnalysisMap; 
	private boolean analysisComplete = false;

	@In
	private GeneralHelper generalHelper;// = (GeneralHelper) Component.getInstance("generalHelper");
	
	
	public HND_SpatialZone getSpatialZone() {
		return spatialZone;
	}
	public void setSpatialZone(HND_SpatialZone spatialZone) {
		this.spatialZone = spatialZone;
	}
	
	public Double getDistance() {
		if (distance == null && ruleGroup != null) {
			double maxDistance = 0.0;
			Double comparisonValue;
			for (HND_SpatialRule sr : ruleGroup.getSpatialRules()) {
				if (sr.getRuleOperator() == HND_RuleOperatorType.DISTANCE) {
					comparisonValue = sr.getComparisonParameterValue(); 
					if (comparisonValue != null && comparisonValue > maxDistance)
						maxDistance = comparisonValue;
				}
			}
			distance = maxDistance;
		}
		return distance;
	}
	public void setDistance(Double distance) {
		this.distance = distance;
	}

	public HND_PermitRuleGroup getRuleGroup() {
		return ruleGroup;
	}
	public void setRuleGroup(HND_PermitRuleGroup ruleGroup) {
		this.ruleGroup = ruleGroup;
	}
	
	public boolean isDoDistance() {
		return doDistance;
	}
	public void setDoDistance(boolean doDistance) {
		this.doDistance = doDistance;
	}
	
	public boolean isDoOverlaps() {
		return doOverlaps;
	}
	public void setDoOverlaps(boolean doOverlaps) {
		this.doOverlaps = doOverlaps;
	}
	
	public boolean isDoWithin() {
		return doWithin;
	}
	public void setDoWithin(boolean doWithin) {
		this.doWithin = doWithin;
	}
	
	public boolean isDoTouches() {
		return doTouches;
	}
	public void setDoTouches(boolean doTouches) {
		this.doTouches = doTouches;
	}
	
	public boolean isDoCrosses() {
		return doCrosses;
	}
	public void setDoCrosses(boolean doCrosses) {
		this.doCrosses = doCrosses;
	}
	
	public boolean isDoContains() {
		return doContains;
	}
	public void setDoContains(boolean doContains) {
		this.doContains = doContains;
	}
	
	public List<SpatialAnalysisResult> getDistanceResults() {
		return distanceResults;
	}
	public List<SpatialAnalysisResult> getOverlapsResults() {
		return overlapsResults;
	}
	
	public List<SpatialAnalysisResult> getWithinResults() {
		return withinResults;
	}
	public List<SpatialAnalysisResult> getTouchesResults() {
		return touchesResults;
	}
	
	public List<SpatialAnalysisResult> getCrossesResults() {
		return crossesResults;
	}
	public List<SpatialAnalysisResult> getContainsResults() {
		return containsResults;
	}
	
	private void doOperation(Geometry shapeAnalysis, List<SpatialAnalysisResult> sarList, HND_RuleOperatorType rot) {
		for (HND_SpatialZone spatialZone : HND_SpatialZoneDAO.loadSpatialZonesByGeom(shapeAnalysis, rot))
			sarList.add(new SpatialAnalysisResult(this.spatialZone, spatialZone));
		
		determineAffectingRules(sarList, rot);
	}

	private boolean ruleAffects(HND_SpatialRule spatialRule, HND_SpatialZone spatialZone, SpatialAnalysisResult sar, HND_RuleOperatorType rot) {
		//Check operand type
		if (spatialRule.getRuleOperator() != rot)
			return false;
		
		//Check for First Operand
		if (spatialRule.getLevelOperand1() != null && spatialRule.getLevelOperand1() != spatialZone.getLevel())
			return false;
		if (spatialRule.getLandUseOperand1() != null && spatialRule.getLandUseOperand1() != spatialZone.getCurrentLandUse())
			return false;
		
		//Check for Second Operand in the spatial analysis result list
		if (spatialRule.getLevelOperand2() != null && spatialRule.getLevelOperand2() != sar.getRhsSpatialZone().getLevel())
			return false;
		if (spatialRule.getLandUseOperand2() != null && spatialRule.getLandUseOperand2() != sar.getRhsSpatialZone().getCurrentLandUse())
			return false;
		
		return true;
	}
	private void determineAffectingRules(List<SpatialAnalysisResult> sarList, HND_RuleOperatorType rot) {
		if (ruleGroup != null && spatialZone != null) {
			Set<HND_SpatialRule> spatialRuleSet = ruleGroup.getSpatialRules();
			
			for (SpatialAnalysisResult sar : sarList) {
				for (HND_SpatialRule spatialRule : spatialRuleSet) {
					if (ruleAffects(spatialRule, spatialZone, sar, rot)) {
						sar.getSpatialRuleSet().add(spatialRule);
						
						List<SpatialAnalysisResult> ruleSarList;
						if (!getSpatialRuleToAnalysisMap().containsKey(spatialRule)) {
							ruleSarList = new ArrayList<SpatialAnalysisResult>();
							ruleSarList.add(sar);
							getSpatialRuleToAnalysisMap().put(spatialRule, ruleSarList);
						}
						else {
							ruleSarList = getSpatialRuleToAnalysisMap().get(spatialRule);
							ruleSarList.add(sar);
						}
					}
				}
			}
		}
	}

	public void doNewSpatialAnalysis() {
		if (spatialZone == null || spatialZone.getShape() == null) return;
		
		Geometry shapeAnalysis = spatialZone.getShape();
		Geometry zoneAux;
		
		spatialRuleToAnalysisMap = null;
		
		if (doDistance) {
			distanceResults = new ArrayList<SpatialAnalysisResult>();
			zoneAux = shapeAnalysis;
			if (getDistance() != null) {
				zoneAux = shapeAnalysis.buffer(getDistance());
				zoneAux.setSRID(shapeAnalysis.getSRID());
			}
			doOperation(zoneAux, distanceResults, HND_RuleOperatorType.DISTANCE);
		}
		if (doOverlaps) {
			overlapsResults = new ArrayList<SpatialAnalysisResult>();
			doOperation(shapeAnalysis, overlapsResults, HND_RuleOperatorType.OVERLAP);
		}
		if (doWithin) {
			withinResults = new ArrayList<SpatialAnalysisResult>();
			doOperation(shapeAnalysis, withinResults, HND_RuleOperatorType.WITHIN);
		}
		if (doTouches) {
			touchesResults = new ArrayList<SpatialAnalysisResult>();
			doOperation(shapeAnalysis, touchesResults, HND_RuleOperatorType.TOUCH);
		}
		if (doCrosses) {
			crossesResults = new ArrayList<SpatialAnalysisResult>();
			doOperation(shapeAnalysis, crossesResults, HND_RuleOperatorType.CROSS);
		}
		if (doContains) {
			containsResults = new ArrayList<SpatialAnalysisResult>();
			doOperation(shapeAnalysis, containsResults, HND_RuleOperatorType.CONTAINS);
		}

		
		analysisComplete = true;
		
		
	}

	


	
	public LA_Level getOperand1Level() {
		return operand1Level;
	}
	public void setOperand1Level(LA_Level operand1Level) {
		this.operand1Level = operand1Level;
	}
	
	public HND_LandUse getOperand1LandUse() {
		return operand1LandUse;
	}
	public void setOperand1LandUse(HND_LandUse operand1LandUse) {
		this.operand1LandUse = operand1LandUse;
	}
	
	
	public LA_Level getOperand2Level() {
		return operand2Level;
	}
	public void setOperand2Level(LA_Level operand2Level) {
		this.operand2Level = operand2Level;
	}

	public HND_LandUse getOperand2LandUse() {
		return operand2LandUse;
	}
	public void setOperand2LandUse(HND_LandUse operand2LandUse) {
		this.operand2LandUse = operand2LandUse;
	}
	
	
	
	public boolean isAnalysisComplete() {
		return analysisComplete;
	}
	public void setAnalysisComplete(boolean analysisComplete) {
		this.analysisComplete = analysisComplete;
	}
	
	

	public Map<HND_SpatialRule, List<SpatialAnalysisResult>> getSpatialRuleToAnalysisMap() {
		if (spatialRuleToAnalysisMap == null) {
			spatialRuleToAnalysisMap = new HashMap<HND_SpatialRule, List<SpatialAnalysisResult>>();
		}
		return spatialRuleToAnalysisMap;
	}
	
	public List<SpatialAnalysisResult> sarListFromRule(HND_SpatialRule spatialRule) {
		List<SpatialAnalysisResult> sarList;
		if (getSpatialRuleToAnalysisMap().containsKey(spatialRule))
			sarList = getSpatialRuleToAnalysisMap().get(spatialRule);
		else
			sarList = new ArrayList<SpatialAnalysisResult>();
		return sarList;
	}
	
	public List<HND_SpatialRule> getRuleGroupRules() {
		if (ruleGroup != null) {
			List<HND_SpatialRule> spatialRuleList = new ArrayList<HND_SpatialRule>();
			for (HND_SpatialRule rule : ruleGroup.getSpatialRules())
				spatialRuleList.add(rule);
			return spatialRuleList;
		}
		return null;
	}
	
	
	public class SpatialAnalysisResult {
		private HND_SpatialZone lhsSpatialZone;
		private HND_SpatialZone rhsSpatialZone;
		private Set<HND_SpatialRule> spatialRuleSet;
		private String analystObservation;
		
		public SpatialAnalysisResult(HND_SpatialZone lhsSpatialZone, HND_SpatialZone rhsSpatialZone) {
			this.lhsSpatialZone = lhsSpatialZone;
			this.rhsSpatialZone = rhsSpatialZone;
		}


		public HND_SpatialZone getLhsSpatialZone() {
			return lhsSpatialZone;
		}

		public HND_SpatialZone getRhsSpatialZone() {
			return rhsSpatialZone;
		}

		public Set<HND_SpatialRule> getSpatialRuleSet() {
			if (spatialRuleSet == null) {
				spatialRuleSet = new HashSet<HND_SpatialRule>();
			}
			return spatialRuleSet;
		}

		public HND_SpatialRule[] getSpatialRuleArray() {
			HND_SpatialRule[] spatialRules = new HND_SpatialRule[getSpatialRuleSet().size()];
			
			int i = 0;
			for (HND_SpatialRule sr : getSpatialRuleSet())
				spatialRules[i++] = sr;
			
			return spatialRules;
		}
		
		public double getDistance() {
			if (lhsSpatialZone != null && rhsSpatialZone != null)
				return lhsSpatialZone.getShape().distance(rhsSpatialZone.getShape());
			
			return 0.0;
		}
		

		public String getAnalystObservation() {
			return analystObservation;
		}
		public void setAnalystObservation(String analystObservation) {
			this.analystObservation = analystObservation;
		}


		public long getSuID() {
			return rhsSpatialZone.getSuID();
		}
		public String getZoneName() {
			return generalHelper.formatZoneNameText(rhsSpatialZone);
		}
		public String getLocationInCountry() {
			return generalHelper.formatNullText(rhsSpatialZone.getLocationInCountry());
		}
		public String getCurrentLandUse() {
			return generalHelper.formatLandUseText(rhsSpatialZone.getCurrentLandUse());
		}
		public String getProposedLandUse() {
			return generalHelper.formatLandUseText(rhsSpatialZone.getProposedLandUse());
		}
		public String getLevelName() {
			return generalHelper.formatLevelText(rhsSpatialZone.getLevel());
		}
	}
	
}
